package logica;

import gui.IView;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.LinkedList;
import java.util.Random;
import java.util.Vector;
import javax.swing.Timer;
import quadtree.QuadTree;

/**
 * Deze klasse bevat zowel de gebieden als de dieren
 * en is een verruiming van de klasse QuadTree
 *
 * @see QuadTree
 * @see IGebied
 * @see IView
 *
 * @author Bart Bruynooghe
 * @author Benjamin De Clercq
 * @author Thomas Flamant
 * @author Sander Van den Broecke
 */
public class Zee extends QuadTree {

	private LinkedList<Dier> dieren;
	private IGebied[][] gebieden;
	private IView view;
	private Timer timer;
	private Boid moetOpgegetenWorden = null;

	/**
	 * Deze methode update de QuadTree.
	 */
	public void update() {
		clear();// clear quadtree
		for (Dier d : dieren) {
			Point2D.Double p = d.getPositie();
			put((float) p.y, (float) p.x, d);
		}
		for (Dier d : dieren) {
			if (d instanceof Orka) {
				Point2D.Double p = d.getPositie();
				remove((float) p.y, (float) p.x, d);
				d.update();
				put((float) p.y, (float) p.x, d);
			} else {
				d.update();
			}
		}
		if (moetOpgegetenWorden != null) {
			dieren.remove(moetOpgegetenWorden);
			moetOpgegetenWorden = null;
		}
		view.updateView();
	}

	public IGebied[][] getGebieden() {
		return gebieden;
	}

	/**
	 * Deze methode geeft een LinkedList van dieren terug
	 * die zich in een bepaalde straal bevinden.
	 * @return	een LinkedList van dieren
	 */
	public LinkedList<Dier> getDieren() {
		return dieren;
	}

	public IView getView() {
		return view;
	}

	// constructor
	/**
	 * De constructor van de klasse Zee.
	 * Er worden gebieden met randen en obstakels gegenereerd als ook
	 * het aanmaken van de dieren en het starten van de timer.
	 */
	public Zee() {
		super(Constanten.AANTALGEBIEDENHOOGTE * Constanten.ZIJDEGEBIED, 0, 0, Constanten.AANTALGEBIEDENBREEDTE * Constanten.ZIJDEGEBIED, Constanten.AANTALVISSEN + Constanten.AANTALORKAS);
		// Gebieden aanmaken
		// Randen
		gebieden = new IGebied[Constanten.AANTALGEBIEDENBREEDTE][Constanten.AANTALGEBIEDENHOOGTE];

		for (int i = 0; i < Constanten.AANTALGEBIEDENBREEDTE; i++) {
			gebieden[0][i] = new Obstakel(new Point2D.Double(0, i));
			gebieden[Constanten.AANTALGEBIEDENHOOGTE - 1][i] = new Obstakel(new Point2D.Double(Constanten.AANTALGEBIEDENHOOGTE - 1, i));
		}
		for (int i = 1; i < Constanten.AANTALGEBIEDENHOOGTE - 1; i++) { // 1 en -1 omdat de bovenste en onderste rij al bestaat
			gebieden[i][0] = new Obstakel(new Point2D.Double(i, 0));
			gebieden[i][Constanten.AANTALGEBIEDENBREEDTE - 1] = new Obstakel(new Point2D.Double(i, Constanten.AANTALGEBIEDENBREEDTE - 1));
		}
		// Obstakels
		Random r = new Random();
		for (int i = 0; i < Constanten.AANTALOBSTAKELS; i++) {
			int x = r.nextInt(Constanten.AANTALGEBIEDENBREEDTE - 1); // genereerd getal tussen de reeds gezette randen
			int y = r.nextInt(Constanten.AANTALGEBIEDENBREEDTE - 1);
			while ((gebieden[y][x] != null)) { //zoek naar leeg gebied
				x = r.nextInt(Constanten.AANTALGEBIEDENBREEDTE - 1); // genereerd getal tussen de reeds gezette randen
				y = r.nextInt(Constanten.AANTALGEBIEDENBREEDTE - 1);
			}
			gebieden[y][x] = new Obstakel(new Point2D.Double(x, y));
		}

		for (int i = 1; i < Constanten.AANTALGEBIEDENBREEDTE - 1; i++) {
			for (int j = 1; j < Constanten.AANTALGEBIEDENHOOGTE - 1; j++) {
				if ((gebieden[j][i] == null)) {
					gebieden[j][i] = new Water(new Point2D.Double(0, 0));
				}
			}
		}

		// Dieren
		dieren = new LinkedList<Dier>();
		int breedte = Constanten.AANTALGEBIEDENBREEDTE * Constanten.ZIJDEGEBIED;
		int hoogte = Constanten.AANTALGEBIEDENHOOGTE * Constanten.ZIJDEGEBIED;
		for (int i = 0; i < Constanten.AANTALVISSEN; i++) {
			int x = r.nextInt(breedte);
			int y = r.nextInt(hoogte);
			while (!isVrij(new Point2D.Double(x, y))) {
				x = r.nextInt(breedte);
				y = r.nextInt(hoogte);
			}
			double richting = r.nextDouble() * Math.PI * 2;
			dieren.add(new Boid(this, new Point2D.Double(x, y), richting));
		}

		for (int i = 0; i < Constanten.AANTALORKAS; i++) {
			int x = r.nextInt(breedte);
			int y = r.nextInt(hoogte);
			while (!isVrij(new Point2D.Double(x, y))) {
				x = r.nextInt(breedte);
				y = r.nextInt(hoogte);
			}
			double richting = r.nextDouble() * Math.PI * 2;
			dieren.add(new Orka(this, new Point2D.Double(x, y), richting));
		}

		// Timer
		timer = new Timer(Constanten.TIMERDELAY, new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				update();
			}
		});
		timer.start();
	}

	/**
	 * De isVrij()-methode controleert of een dier niet te dicht bij een obstakel komt.
	 * Indien dit wel het geval is wordt de richting bijgesteld.
	 * @param positie	de positie van het dier
	 * @return			een boolean die aangeeft of het dier te dicht bij een obstakel komt
	 */
	private boolean isVrij(Point2D.Double positie) {
		//zoek gebied
		boolean isTeDichtBijObstakel = false;
		for (int i = 0; i < Constanten.AANTALGEBIEDENBREEDTE; i++) {
			for (int j = 0; j < Constanten.AANTALGEBIEDENHOOGTE; j++) {
				if ((gebieden[j][i] instanceof Obstakel)) {
					if (((Obstakel) gebieden[j][i]).getAfstand(positie) < Constanten.AFSTANDTOTOBSTAKEL) {
						isTeDichtBijObstakel = true;
					}
				}
			}
		}
		return !isTeDichtBijObstakel;
	}

	public void setView(IView view) {
		this.view = view;
	}

	/**
	 * Deze methode geeft een LinkedList van dieren terug die zich in een bepaalde straal bevinden.
	 * @param straal	de straal van de cirkel waarin gezocht wordt naar dieren
	 * @param positie	het middelpunt van de cirkel waarin gezocht wordt
	 * @return			een LinkedList van alle dieren binnen de cirkel
	 */
	public LinkedList<Dier> getDieren(int straal, Point2D.Double positie) {
		float y = (float) positie.y;
		float x = (float) positie.x;
		Vector<Dier> dierenInVierkant = get(y + straal, x - straal, y - straal, x + straal);
		LinkedList<Dier> dierenInCirkel = new LinkedList<Dier>();
		for (Dier d : dierenInVierkant) {
			dierenInCirkel.add(d);
		}
		return dierenInCirkel;
	}

	/**
	 * Deze methode geeft het dichtstbijzijnde dier terug..
	 * @param straal	de straal van de cirkel waarin gezocht wordt naar dieren
	 * @param positie	het middelpunt van de cirkel waarin gezocht wordt
	 * @return			het dichtstbijzijnde dier binnen de cirkel
	 */
	public Dier getDichtsteDier(int straal, Point2D.Double positie) {
		int y = (int) positie.y;
		int x = (int) positie.x;
		return (Dier) get(y, x, (double) straal);
	}

	/**
	 * Deze methode geeft aan de hand van de positie het gebied terug.
	 * @param positie	het punt waarvan het gebied gevraagd wordt
	 * @return			het gebied waarin het opgegeven punt ligt
	 */
	public IGebied getGebied(Point2D.Double positie) {
		int x = (int) (positie.x / Constanten.ZIJDEGEBIED);
		int y = (int) (positie.y / Constanten.ZIJDEGEBIED);
		return gebieden[y][x];
	}

	/**
	 * Deze methode geeft een LinkedList van obstakels terug, berekend aan de hand van de positie.
	 * @param positie	het punt waarrond naar obstakels gezocht moet worden
	 * @return			een LinkedList met alle gevonden obstakels
	 */
	public LinkedList<Obstakel> getOmliggendeObstakels(Point2D.Double positie) {
		LinkedList<Obstakel> omliggende = new LinkedList<Obstakel>();
		for (int i = 0; i < Constanten.AANTALGEBIEDENBREEDTE; i++) {
			for (int j = 0; j < Constanten.AANTALGEBIEDENHOOGTE; j++) {
				if ((gebieden[j][i] instanceof Obstakel)) {
					omliggende.add((Obstakel) gebieden[j][i]);
				}
			}
		}
		return omliggende;
	}

	/**
	 * Deze methode start de timer.
	 */
	public void start() {
		timer.start();
	}

	/**
	 * Deze methode stopt de timer.
	 */
	public void stop() {
		timer.stop();
	}

	/**
	 * Deze methode berekent op basis van de parameter value de nieuwe snelheid
	 * van de timer en bijgevolg van de boids en orkas.
	 * @param value		indicatie voor het aanpassen van de timertickfrequentie
	 */
	public void setSnelheidTimer(int value) {
		int newDelay = 0;
		if (value < 0) {
			newDelay = Constanten.TIMERDELAY * Math.abs(value);
		} else if (value > 0) {
			newDelay = Constanten.TIMERDELAY / value;
		} else {
			newDelay = Constanten.TIMERDELAY;
		}
		timer.setDelay(newDelay);
	}

	/**
	 * Deze methode markeert een boid die moet verwijderd worden in de lijst van dieren.
	 * @param b		de te verwijderen boid
	 */
	public void eetBoid(Boid b) {
		moetOpgegetenWorden = b;
	}
}
